import {celebrate, Joi} from "celebrate";
import {withLock} from "easylock";
import * as express from "express";
import {Request} from "express";
import {v4 as uuid} from 'node-uuid'
import {InstanceType} from "typegoose";
import {isNullOrUndefined} from "util";
import {Cdkey, CdkeyModel} from "../database/models/cdkey";
import {CdkeyRecordModel} from "../database/models/cdkeyRecord";
import {GamePrizeModel} from "../database/models/gamePrize";
import GlobalModel from "../database/models/global";
import GM from "../database/models/gm";
import {
  LipstickSeries,
  LipstickSeriesModel,
  PlayerSeriesIncomeModel,
  SeriesIncomeModel
} from "../database/models/lipstickSeries";
import {GiftState, MailModel, MailState, MailType} from "../database/models/mail";
import {Player, PlayerModel} from "../database/models/player";
import {ProductModel, RMB_2_COIN_RATE} from "../database/models/Product";
import {getAfterReduceMostStockProductBySeries} from "../database/models/ProductGroup";
import {DEFAULT_THRESHOLD_RATE, SeriesIncomeThresholdModel} from "../database/models/seriesIncomeThreshold";
import createClient from "../utils/redis";
import {InternalError} from "./error";
import {getDaPan} from "./games/lipStickGame";
import {jwtSign} from "./jwt";
import {Response, withCdKey, withLipstickSeries, withType, withUser} from "./middlewareType";
import {jwtAuthMiddleware} from "./passort";
import {
  freeCoinByInviter,
  freeCoinBySharer,
  freeCoinBySharing,
  freeCoinDefault,
  pcUrl,
  putDataInBroadcastInfo
} from "./utils";
import * as request from 'superagent'

const playerRouter = express.Router()
// tslint:disable-next-line:no-var-requires
const QcloudSms = require('qcloudsms_js')

const redisClient = createClient()

if (true) {
// if (process.env.NODE_ENV !== 'production') {
  playerRouter.post('/login/tourist', async (req, res) => {
    const updater = {$inc: {shortIdCounter: 1}}
    const options = {new: true}
    const global = await GlobalModel.findOneAndUpdate({}, updater, options)
    const shortId = global.shortIdCounter;

    const player = {
      name: `游客 ${shortId}`,
      gem: 0,
      headImgUrl: '',
      sex: 0,
      freeCoin: freeCoinDefault,
      shortId,
      freeGold: 0,
      isTourist: true,
      cookie: uuid()
    }

    const playerModel = await PlayerModel.create(player)

    const token = jwtSign({
      _id: playerModel.id, cookie: playerModel.cookie
    })

    res.json({
      ok: true, data: {
        player: playerModel,
        token
      }
    })
  })
}

playerRouter.get('/info', jwtAuthMiddleware, (req: any, res) => {
  const size = `300x300`
  const shareUrl = `http://lipstick.51ulong.com/public/register.html?shareCode=${req.user.shortId}`
  const qrCodePngUrl = `http://api.qrserver.com/v1/create-qr-code/?size=${size}&data=${shareUrl}`
  res.json({
    ok: true,
    data: {
      player: req.user,
      qrCodePngUrl
    }
  })
})

playerRouter.post('/changeInfo',
  jwtAuthMiddleware,
  celebrate({
    body: Joi.object({
      name: Joi.string().allow(''),
      sex: Joi.number().allow(0)
    })
  }),
  async (req: Request & withUser & withLipstickSeries, res: Response, next) => {
    try {
      if (req.body.name) {
        if (req.body.name === "")
          throw InternalError("EMPTY_NICKNAME_REFUSED")
        else if (req.body.name.length > 15)
          throw InternalError("TOO_LONG_NICKNAME")
        else
          await PlayerModel.findByIdAndUpdate(req.user, {$set: {name: req.body.name}})
      }

      if (req.body.sex !== undefined) {
        if (req.body.sex !== 0 && req.body.sex !== 1)
          throw InternalError("ERROR_SEX")
        else
          await PlayerModel.findByIdAndUpdate(req.user, {$set: {sex: req.body.sex}})
      }

      return res.json({
        ok: true
      })
    } catch (e) {
      res.error(e)
    }

  })

playerRouter.get('/income/:lipstickSeriesId',
  jwtAuthMiddleware,
  celebrate({
    params: Joi.object({
      lipstickSeriesId: Joi.string().required()
    })
  }),
  async (req: Request & withLipstickSeries, res: Response, next) => {
    const ls = await LipstickSeriesModel.findById(req.params.lipstickSeriesId)

    if (!ls)
      return res.error(InternalError("NO_SUCH_PRODUCT"))

    req.lipstickSeries = ls
    next()
  },
  async (req: Request & withUser & withLipstickSeries, res: Response) => {
    try {

      await withLock(req.user.id, async () => {
        const psi = await PlayerSeriesIncomeModel.findOne({
          lipstickSeries: req.params.lipstickSeriesId,
          player: req.user.id
        })

        if (!psi) {
          throw InternalError('NO_SUCH_PRODUCT')
        }

        let threshold
        const sit = await SeriesIncomeThresholdModel.findOne({
          lipstickSeries: req.lipstickSeries._id
        })

        if (!sit) {
          throw InternalError('NO_SUCH_PRODUCT')
        }
        threshold = req.lipstickSeries.seriesPrice * RMB_2_COIN_RATE * sit.thresholdCoinRate

        return res.json({
          ok: true,
          data: {
            canUse: psi.income >= threshold,
            percentage: psi.income / threshold
          }
        })
      })
    } catch (e) {
      res.error(e)
    }
  })

playerRouter.post('/income/prize/:lipstickSeriesId',
  jwtAuthMiddleware,
  celebrate({
    params: Joi.object({
      lipstickSeriesId: Joi.string().required()
    })
  }),
  async (req: Request & withLipstickSeries, res: Response, next) => {
    const ls: InstanceType<LipstickSeries> =
      await LipstickSeriesModel.findById(req.params.lipstickSeriesId)

    if (!ls)
      return res.error(InternalError("NO_SUCH_SERIES"))

    if (!ls.onStock)
      return res.error(InternalError("NO_ON_STOCK"))

    req.lipstickSeries = ls
    next()
  },
  async (req: Request & withUser & withLipstickSeries, res: Response) => {

    try {

      await withLock(req.user.id, async () => {

        const psi = await PlayerSeriesIncomeModel.findOne({
          lipstickSeries: req.lipstickSeries,
          player: req.user.id
        })
        if (!psi) {
          throw InternalError('NO_SUCH_PRODUCT')
        }

        let threshold
        let rate
        const sit = await SeriesIncomeThresholdModel.findOne({
          product: req.lipstickSeries._id
        })

        if (!sit)
          rate = DEFAULT_THRESHOLD_RATE
        else
          rate = sit.thresholdCoinRate

        threshold = req.lipstickSeries.seriesPrice * RMB_2_COIN_RATE * rate

        if (psi.income < threshold)
          throw InternalError('NO_ENOUGH_INCOME')

        const {ok, data} = await getAfterReduceMostStockProductBySeries(req.lipstickSeries._id)
        if (!ok) {
          await LipstickSeriesModel.findByIdAndUpdate(req.lipstickSeries.id, {$set: {onStock: false}})
          throw InternalError(data.message)
        }
        if (!data.productId) {
          await LipstickSeriesModel.findByIdAndUpdate(req.lipstickSeries.id, {$set: {onStock: false}})
          throw InternalError("NO_SUCH_PRODUCT")
        }
        const p = await ProductModel.findById(data.productId)
        if (!p)
          throw InternalError("NO_SUCH_PRODUCT")

        psi.income = 0
        await psi.save()

        await SeriesIncomeModel.update({
          lipstickSeries: req.lipstickSeries.id,
        }, {
          $inc: {income: -await getDaPan(req.lipstickSeries)},
        }, {upsert: true})

        const prize = await GamePrizeModel.create({
          player: req.user._id,
          lipstickSeries: req.lipstickSeries._id,
          product: p._id,
          productName: p.name,
          productModel: p.model,
          productDescription: p.description,
          productUrl: p.coverUrl,
          inviteBy: req.user.inviteBy,
          state: 'idle',
          createAt: new Date(),
          from: 'luckyGift',
          selectState: `waitSelect`
        })

        await MailModel.create({
          title: `兑换奖励成功`,
          content: `${prize.productName}已成功兑换`,
          to: prize.player,
          type: MailType.NOTICE,
          gift: {prize: prize._id},
          giftState: GiftState.NOTALLOW,
          state: MailState.UNREAD
        })
        const broadcastInfo = {
          name: req.user.name,
          from: req.lipstickSeries.name,
          info: `使用幸运值兑换了一个：${prize.productName}`,
          createAt: new Date(Date.now())
        }
        putDataInBroadcastInfo(broadcastInfo)

        return res.json({ok: true, data: {prize}})
      })
    } catch (e) {
      res.error(e)
    }
  })

playerRouter.post('/getSMSCode',
  celebrate({
    body: Joi.object({
      phoneNum: Joi.string().required(),
      gameType: Joi.string().default('')
    })
  }),
  async (req: Request & withUser & withType, res: Response) => {
    if (!req.body.phoneNum || req.body.phoneNum.length !== 11)
      return res.json({ok: false, data: {msg: `手机号错误`}})

    let tempDoc = await PlayerModel.findOne({phone: req.body.phoneNum}).lean()

    if (req.body.gameType === 'pucheng' && !tempDoc) {
      const result = await request.post(pcUrl + '/provideForOtherGame/findPlayer')
        .send({phoneNum: req.body.phoneNum})
        .then(r => {
          if (r.body.ok) {
            return r.body
          } else {
            return {ok: false}
          }
        }).catch(e => {
          console.error('浦城 login err ', e)
          return {ok: false}
        })

      if (result.ok) {
        const updater = {$inc: {shortIdCounter: 1}}
        const options = {new: true}
        const global = await GlobalModel.findOneAndUpdate({}, updater, options)
        const shortId = global.shortIdCounter;

        const getData = result.data
        const player = {
          wechatUnionid: getData._id,
          name: getData.name || shortId,
          gem: 0,
          headImgUrl: getData.headImgUrl || '',
          sex: getData.sex || 0,
          phone: getData.phone,
          freeCoin: freeCoinDefault,
          shortId,
          freeGold: 0,
          isTourist: false,
          cookie: uuid()
        }
        tempDoc = await PlayerModel.create(player)
      } else {
        return res.json({ok: false, data: {msg: `没有找到浦城玩家相关信息`}})
      }
    }
    req.smsCodeType = tempDoc ? 'login' : 'register'
    // req.user = tempDoc === 'login' ? tempDoc : null

    const smsCode = await redisClient.lpopAsync(`smsCodes`)
    if (!smsCode)
      return res.json({ok: false, data: {msg: `验证码生成错误`}})

    await redisClient.rpushAsync('smsCodes', smsCode)
    const countDown = 1

    await sendSMS(req, res, req.body.phoneNum, smsCode, countDown)
  }
)

playerRouter.post('/bindPhone',
  jwtAuthMiddleware,
  celebrate({
    body: Joi.object({
      phoneNum: Joi.string().required(),
      smsCode: Joi.string().required()
    })
  }),
  async (req: Request & withUser, res: Response) => {
    if (isNullOrUndefined(req.body.phoneNum) || req.body.phoneNum.length !== 11)
      return res.json({ok: false, data: {msg: `请输入正确手机号`}})

    if (req.body.smsCode.length !== 6)
      return res.json({ok: false, data: {msg: `请输入正确验证码`}})

    const p: InstanceType<Player> = await PlayerModel.findById(req.user.id).lean()
    if (!p)
      return res.json({ok: false, data: {msg: `该用户还没有注册呢，请先注册喔~`}})

    const tempDoc = await PlayerModel.findOne({phone: req.body.phoneNum}).lean()
    if (tempDoc)
      return res.json({ok: false, data: {msg: `手机号已绑定其他账号`}})

    const smsCode = await redisClient.getAsync(req.body.phoneNum)
    if (!smsCode)
      return res.json({ok: false, data: {msg: `验证码超时或验证码错误`}})

    if (req.body.smsCode.toString() !== smsCode)
      return res.json({ok: false, data: {msg: `请输入正确验证码`}})

    await PlayerModel.findOneAndUpdate(
      {_id: req.user.id},
      {$set: {phone: req.body.phoneNum}})

    return res.json({ok: true, data: {msg: `手机号绑定成功`}})
  }
)

playerRouter.post('/register/invite',
  celebrate({
    body: Joi.object({
      phoneNum: Joi.string().required(),
      smsCode: Joi.string().required(),
      inviteCode: Joi.string().optional().allow(''),
      shareCode: Joi.number().allow(0),
      requestBy: Joi.string().required().valid(['app', 'html'])
    })
  }),
  async (req: Request & withUser, res: Response) => {

    if (isNullOrUndefined(req.body.phoneNum) || req.body.phoneNum.length !== 11)
      return res.json({ok: false, data: {msg: `请输入正确手机号`}})

    if (req.body.smsCode.length !== 6)
      return res.json({ok: false, data: {msg: `请输入正确验证码`}})

    const p1 = await PlayerModel.findOne({phone: req.body.phoneNum}).lean()
    if (p1)
      return res.json({ok: false, data: {msg: `手机号已绑定其他账号`}})

    const smsCode = await redisClient.getAsync(req.body.phoneNum)
    if (!smsCode)
      return res.json({ok: false, data: {msg: `验证码超时或验证码错误`}})

    if (req.body.smsCode.toString() !== smsCode)
      return res.json({ok: false, data: {msg: `请输入正确验证码`}})

    const updater = {$inc: {shortIdCounter: 1}}
    const options = {new: true}
    const global = await GlobalModel.findOneAndUpdate({}, updater, options)
    const shortId = global.shortIdCounter;
    const phoneTail = req.body.phoneNum.slice(-4)

    const player = {
      name: `手机用户 ${phoneTail}`,
      shortId,
      gem: 0,
      headImgUrl: '',
      sex: 0,
      freeCoin: freeCoinDefault,
      isTourist: false,
      cookie: uuid(),
      phone: req.body.phoneNum,
    }

    let gm = null
    if (!isNullOrUndefined(req.body.inviteCode) && req.body.inviteCode !== "")
      gm = await GM.findOne({inviteCode: req.body.inviteCode})
    if (gm) {
      player[`inviteBy`] = gm._id
      player.freeCoin += freeCoinByInviter
    }

    let shareEr = null
    if (req.body.shareCode && req.body.shareCode !== 0)
      shareEr = await PlayerModel.findOne({shortId: req.body.shareCode})
    if (shareEr) {
      player[`shareBy`] = shareEr._id
      player.freeCoin += freeCoinBySharer

      if (shareEr.sharedCount < 10) {
        await PlayerModel.findByIdAndUpdate(shareEr._id,
          {$inc: {freeCoin: freeCoinBySharing, sharedCount: 1}})
      }
    }
    const p = await PlayerModel.create(player)

    if (req.body.requestBy === 'app') {
      const token = jwtSign({
        _id: p.id, cookie: p.cookie
      })
      res.json({
        ok: true, data: {
          player,
          token
        }
      })
    } else if (req.body.requestBy === 'html') {
      res.json({ok: true, data: {msg: `注册成功！请登录后领取奖励！`}})
    }
  }
)

playerRouter.post('/smsLogin/login',
  celebrate({
    body: Joi.object({
      phoneNum: Joi.string().required(),
      smsCode: Joi.string().required()
    })
  }),
  async (req: Request & withUser, res: Response) => {

    if (isNullOrUndefined(req.body.phoneNum) || req.body.phoneNum.length !== 11)
      return res.json({ok: false, data: {msg: `请输入正确手机号`}})

    if (req.body.smsCode.length !== 6)
      return res.json({ok: false, data: {msg: `请输入正确验证码`}})

    const smsCode = await redisClient.getAsync(req.body.phoneNum)
    if (!smsCode)
      return res.json({ok: false, data: {msg: `验证码超时或验证码错误`}})

    if (req.body.smsCode.toString() !== smsCode)
      return res.json({ok: false, data: {msg: `请输入正确验证码`}})

    const player = await PlayerModel.findOne({phone: req.body.phoneNum}).lean()
    if (!player)
      return res.json({ok: false, data: {msg: `该用户还没有注册呢，请先注册喔~`}})

    const playerNew = await PlayerModel.findByIdAndUpdate(player._id, {$set: {cookie: uuid()}}, {new: true})
    const token = jwtSign({
      _id: playerNew.id, cookie: playerNew.cookie
    })

    res.json({
      ok: true, data: {
        player,
        token
      }
    })
  })

playerRouter.post('/bindGM',
  jwtAuthMiddleware,
  celebrate({
    body: Joi.object({
      inviteCode: Joi.string()
    })
  }),
  async (req: Request & withUser, res: Response) => {

    const p: InstanceType<Player> = await PlayerModel.findById(req.user.id).lean()
    if (!p)
      return res.json({ok: false, data: {msg: `无此用户`}})

    if (p.inviteBy)
      return res.json({ok: false, data: {msg: `您已绑定过啦`}})

    if (req.body.inviteCode.length !== 6)
      return res.json({ok: false, data: {msg: `邀请码错误`}})

    let gm = null
    if (!isNullOrUndefined(req.body.inviteCode) && req.body.inviteCode !== "")
      gm = await GM.findOne({inviteCode: req.body.inviteCode})

    if (!gm)
      return res.json({ok: false, data: {msg: `邀请码错误`}})

    await PlayerModel.findByIdAndUpdate(req.user.id, {$set: {inviteBy: gm._id}, $inc: {freeCoin: freeCoinByInviter}})

    res.json({ok: true, data: {msg: `邀请码绑定成功`, coin: freeCoinByInviter}})
  }
)

playerRouter.post('/bindPlayer',
  jwtAuthMiddleware,
  celebrate({
    body: Joi.object({
      shareCode: Joi.number().allow(0),
    })
  }),
  async (req: Request & withUser, res: Response) => {

    const p: InstanceType<Player> = await PlayerModel.findById(req.user.id).lean()
    if (!p)
      return res.json({ok: false, data: {msg: `无此用户`}})

    if (p.shareBy)
      return res.json({ok: false, data: {msg: `您已绑定过啦`}})

    let shareEr = null
    if (req.body.shareCode && req.body.shareCode !== 0)
      shareEr = await PlayerModel.findOne({shortId: req.body.shareCode})

    if (!shareEr)
      return res.json({ok: false, data: {msg: `分享码错误`}})

    if (shareEr.sharedCount < 10) {
      await PlayerModel.findByIdAndUpdate(shareEr._id,
        {$inc: {freeCoin: freeCoinBySharing, sharedCount: 1}})
    }

    await PlayerModel.findByIdAndUpdate(p._id,
      {$set: {shareBy: shareEr._id}, $inc: {freeCoin: freeCoinBySharer, sharedCount: 1}})

    res.json({ok: true, data: {msg: `邀请码绑定成功`, coin: freeCoinBySharer}})
  }
)

async function sendSMS(req, res, phoneNum, smsCode, countDown = 1) {
  const appid = 1400195158
  const appkey = "b50da51c76bcfc20d61e79368e49e15b"
  const phoneNumbers = [phoneNum]

  const templateId = 302128

  const smsSign = ""
  const qcloudsms = QcloudSms(appid, appkey)

  async function callback(err, ressss, resData) {
    if (err) {
      console.log("err: ", err)
      return res.json({ok: false, data: {msg: err}})
    } else {
      await redisClient.setAsync(phoneNum, smsCode, "ex", countDown * 60)
      return res.json({ok: true, data: {msg: "短信已发送", countDown, type: req.smsCodeType}})
    }
  }

  const sSender = qcloudsms.SmsSingleSender()
  const params = [smsCode, countDown]

  if (process.env.NODE_ENV === 'test') {
    await redisClient.setAsync(phoneNum, smsCode, "ex", countDown * 60)
    return res.json({ok: true, data: {msg: "短信已发送", countDown, type: req.smsCodeType}})
  }

  sSender.sendWithParam(86, phoneNumbers[0], templateId,
    params, smsSign, "", "", callback)

}

playerRouter.post('/cdkey/:cdKeyStr',
  jwtAuthMiddleware,
  celebrate({
    params: Joi.object({
      cdKeyStr: Joi.string().required()
    })
  }),
  async (req: Request & withUser & withCdKey, res: Response) => {
    await withLock(req.user.id, async () => {
      await withLock(req.params.cdKeyStr, async () => {
        try {
          const cdkeys = await CdkeyModel.find({state: 'on'}).populate(`toPlayer`)
          let cdkey: InstanceType<Cdkey> = null
          for (const it of cdkeys) {
            if (it.keys.indexOf(req.params.cdKeyStr) > -1) {
              cdkey = it
              break
            }
          }
          if (!cdkey)
            throw InternalError("ERROR_CD_KEY")

          if (Date.now() > cdkey.expireAt.getTime())
            throw InternalError("EXPIRED_CD_KEY")

          const player = await PlayerModel.findById(req.user.id)
          if (!player)
            throw InternalError("NO_SUCH_USER")

          const cdkeyRecord = await CdkeyRecordModel.findOne({cdkey: cdkey.id, player: req.user.id})
          if (cdkeyRecord)
            throw InternalError("ALREADY_USE_CD_KEY")

          function remove(ary: string[], v) {
            const i = ary.indexOf(v)
            if (i > -1)
              ary.splice(i, 1)
            else
              throw InternalError("ERROR_CD_KEY")
          }

          switch (cdkey.type) {
            case "OneToAll":
              break
            case "OneToSome":
              // @ts-ignore
              if (cdkey.toPlayers.indexOf(req.user.id) < 0)
                throw InternalError("ERROR_PLAYER_LINK_TO_CD_KEY")
              break
            case "AllToAll":
              remove(cdkey.keys, req.params.cdKeyStr)
              await cdkey.save()
              break
            default:
              throw InternalError("ERROR_CD_KEY")
          }

          // player.coin += cdkey.coin
          // player.freeCoin += cdkey.freeCoin
          // player.gem += cdkey.gem
          // player.point += cdkey.point
          // await player.save()

          await PlayerModel.findByIdAndUpdate(req.user.id, {
            $inc: {
              coin: cdkey.coin,
              freeCoin: cdkey.freeCoin,
              gem: cdkey.gem,
              point: cdkey.point
            }
          })

          await CdkeyRecordModel.create({
            cdkey: cdkey.id,
            player: player.id,
            keyWord: req.params.cdKeyStr,
            coin: cdkey.coin,
            freeCoin: cdkey.freeCoin,
            gem: cdkey.gem,
            point: cdkey.point
          })

          await MailModel.create({
            title: `活动兑换码兑换成功`,
            content: `${cdkey.name}兑换码已成功兑换`,
            to: player.id,
            type: MailType.NOTICE,
            gift: {coin: cdkey.coin, freeCoin: cdkey.freeCoin, gem: cdkey.gem, point: cdkey.point},
            giftState: GiftState.NOTALLOW,
            state: MailState.UNREAD
          })

          res.json({
            ok: true, data: {
              msg: cdkey.name,
              coin: cdkey.coin,
              freeCoin: cdkey.freeCoin,
              gem: cdkey.gem,
              point: cdkey.point
            }
          })

        } catch (e) {
          res.error(e)
        }
      })
    })
  })

export default playerRouter
